package srcTA;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import lib.FileIO;
import lib.Utility;
import lib.logging.ILogger;

public class ImprovedHillClimbing 
{
    public static final int MaxRestarts = 600;
    
    private StateManager stateManager;
    private ILogger logger;
    private File outputFile;

    public ImprovedHillClimbing(StateManager stateManager, ILogger logger, File outputFile) 
    {
        this.stateManager = stateManager;
        this.logger = logger;
        this.outputFile = outputFile;
    }
    
    public AcceptedBids GetSolutionWithGreedyInitialState(double timeLimit) throws IOException
    {
        AcceptedBids solutionSetOfBids = null;
        int roundInWhichMaxValueSeen = 0;
        
        long startTime = System.currentTimeMillis();
        
        for (int i = 0; i < MaxRestarts; ++i)
        {
            AcceptedBids currentState = null;
            
            if (i == 0)
            {
                currentState = stateManager.GetGreedyValueInitialState();
            }
            else if (i == 1)
            {
                currentState = stateManager.GetGreedyPerRegionCostInitialState();
            }
            else
            {
                currentState = stateManager.GetGreedyRankBasedInitialState();
            }
            
            while (true)
            {
                List<AcceptedBids> neighbouringStates = stateManager.GetNeighboringStates(currentState);
                
                AcceptedBids nextState = stateManager.SelectHighestNeighbor(neighbouringStates);
                
                if (nextState.TotalValue() <= currentState.TotalValue())
                {
                    break;
                }
                
                currentState = nextState;
            }
            
            if (solutionSetOfBids == null
                || solutionSetOfBids.TotalValue() < currentState.TotalValue())
            {
                solutionSetOfBids = currentState;
                roundInWhichMaxValueSeen = i;
                
                WriteSolutionToFile(currentState.Bids());
            }
            
            List<Integer> bidIdsInCurrentState = new ArrayList<Integer>();
            for (Bid currentBid : currentState.Bids())
            {
                bidIdsInCurrentState.add(currentBid.BidId());
            }
            Collections.sort(bidIdsInCurrentState);
            
            long timeElapsed = GetTimeElapsed(startTime);
            
            logger.LogInfo(
                    "(max seen '%f' in round: %d; timeElapsed:%s sec, timeLimit:%s sec): current round %d: total value: %f bids:[%s]",
                    solutionSetOfBids.TotalValue(),
                    roundInWhichMaxValueSeen,
                    timeElapsed/1000,
                    timeLimit/1000,
                    i,
                    currentState.TotalValue(),
                    Utility.FlattenListToString(bidIdsInCurrentState));
            
            if (timeElapsed >= timeLimit)
            {
                logger.LogInfo("Time limit exceeded! Ending program...");
                break;
            }
        }
        
        Collections.sort(solutionSetOfBids.Bids());
        Collections.sort(solutionSetOfBids.RegionsCovered());
        Collections.sort(solutionSetOfBids.CompaniesCovered());
        return solutionSetOfBids;
    }
    
    public AcceptedBids GetSolutionWithRandomInitialState()
    {
        AcceptedBids solutionSetOfBids = null;
        int roundInWhichMaxValueSeen = 0;
        
        for (int i = 0; i < MaxRestarts; ++i)
        {
            AcceptedBids currentState = stateManager.GetRandomInitialState();
    
            while (true)
            {
                List<AcceptedBids> neighbouringStates = stateManager.GetNeighboringStates(currentState);
                
                AcceptedBids nextState = stateManager.SelectHighestNeighbor(neighbouringStates);
                
                if (nextState.TotalValue() <= currentState.TotalValue())
                {
                    break;
                }
                
                currentState = nextState;
            }
            
            if (solutionSetOfBids == null
                || solutionSetOfBids.TotalValue() < currentState.TotalValue())
            {
                solutionSetOfBids = currentState;
                roundInWhichMaxValueSeen = i;
            }
            
            List<Integer> bidIdsInSolution = new ArrayList<Integer>();
            for (Bid solutionBid : solutionSetOfBids.Bids())
            {
                bidIdsInSolution.add(solutionBid.BidId());
            }
            Collections.sort(bidIdsInSolution);
            
            logger.LogInfo(
                    "(max seen so far '%f' in round: %d): current round %d: total value: %f bids:[%s]",
                    solutionSetOfBids.TotalValue(),
                    roundInWhichMaxValueSeen,
                    i,
                    currentState.TotalValue(),
                    Utility.FlattenListToString(bidIdsInSolution));
        }
        
        Collections.sort(solutionSetOfBids.Bids());
        Collections.sort(solutionSetOfBids.RegionsCovered());
        Collections.sort(solutionSetOfBids.CompaniesCovered());
        return solutionSetOfBids;
    }

    private long GetTimeElapsed(long startTime) 
    {
        long endTime = System.currentTimeMillis();
        return (endTime - startTime);
    }

    private void WriteSolutionToFile(List<Bid> solutionBids) throws IOException 
    {
        StringBuffer sb = new StringBuffer();
        
        for (int i = 0; i < solutionBids.size(); ++i)
        {
            sb.append(solutionBids.get(i).BidId());
            
            if (i != (solutionBids.size() - 1))
            {
                sb.append(" ");
            }
        }
        
        sb.append("#");
        
        FileIO.OverwriteFile(outputFile, sb.toString());
    }
}
